%%%-------------------------------------------------------------------
%%% @author liuwentao
%%% @doc
%%%     敏感词管理模块
%%% @end
%%% Created : 08. 7月 2021 16:04
%%%-------------------------------------------------------------------
-module(keyword).
-author("liuwentao").

%% API
-export([
    create_tire/2                   %% 创建敏感词字典树
    , load_file_keyword/1           %% 加载配置
    , load_file/1                   %% 写入文件
    , check_key_world/1             %% 检查敏感词
    , check/2                       %% 检查是否可用
]).


-include("common.hrl").

%% ======================================构建字典树=====================================
%% @doc 加载配置表构建树
load_file_keyword(Text) ->
    %% 取当前时间做文件名
    NewMap = create_tire(Text, #{}),
    %% 写文件
    load_file(NewMap),
    %% 提示拉取完毕
    ?INFO("敏感词库拉取完毕，已保存到文件中"),
    ok.


%% @doc 构建字典树
create_tire(WorldList, Maps) ->
    %% 实际构建树
    NewMaps = do_create_tire(WorldList, Maps),
    NewMaps.


%% @doc 根据单词构建树
do_create_tire([], Maps) ->
    Maps;
do_create_tire([Str | T], Maps) ->
    %% 转换为unicode编码
    NewStr = unicode:characters_to_list(Str),
    [H | _] = NewStr,
    KeyWord = maps:get(H, Maps, {H, 0, #{}}),
    %% 将敏感词加入树中
    NewKeyWord = do_init_word(KeyWord, NewStr),
    NewMaps = maps:put(H, NewKeyWord, Maps),
    do_create_tire(T, NewMaps).


%% @doc 将敏感词加入树中
do_init_word(KeyWorld = {Id, _, Map}, [Str | T]) ->
    case Id == Str of
        true when T == [] ->
            {Id, ?true, Map};
        _ ->
            do_init_word2(KeyWorld, T)
    end.

%% @doc 将敏感词加入树中
do_init_word2({Id, IsEnd, KeyList}, Msg = [Str | _]) ->
    CkeyWorld1 = maps:get(Str, KeyList, {Str, 0, #{}}),
    NewCkeyWorld = do_init_word(CkeyWorld1, Msg),
    NewKeyList = KeyList#{Str => NewCkeyWorld},
    {Id, IsEnd, NewKeyList}.


%% @doc 将字典树加载进文件
load_file(Maps) ->
    Name = "key_world_data",
    %% 拼完整文件路径
    FileName = "../../server/src/data/" ++ Name ++ ".erl",
    %% 转换为列表
    List = maps:to_list(Maps),
    NewList = [V || {_, V} <- List],
    %% 开启文件流
    {ok, FileRef} = file:open(FileName, [write]),
    %% 拼接字符串
    Text = split_joint(NewList),
    file:write(FileRef, Text),
    file:close(FileRef),
    ok.

%% @doc 拼接文件
split_joint(List) ->
    %% 头部
    Head = "-module(key_world_data).\n-export([get/1]).\n\n",
    %% 尾部
    Last = "get(_) -> false.",
    Text = do_split_joint(List, []),
    Head ++ Text ++ Last.

%% @doc 拼接子项
do_split_joint([], Msg) ->
    Msg;
do_split_joint([{Id, IsEnd, Map} | T], Msg) ->
    AddMsg = lists:concat(["get(", Id, ") ->", "{", Id, ",", IsEnd, ",", io_lib:format("~w", [Map]), "};\n"]),
    do_split_joint(T, AddMsg ++ Msg).


%% @doc 敏感词检查
check_key_world(Msg) ->
    RE = "[-。，、＇：∶；?‘’“”〝〞ˆˇ﹕︰﹔﹖﹑•¨….¸;！´？！～—ˉ｜‖＂〃｀@﹫¡¿﹏﹋﹌︴々﹟#﹩$()﹠&﹪%*﹡﹢﹦﹤‐￣¯―﹨ˆ˜﹍﹎+=<＿_\ˇ~﹉﹊（）〈〉‹›﹛﹜『』〖〗［］《》〔〕{}「」【】 /]",
    Msg1 = re:replace(Msg, RE, "", [{return, list}, global, unicode]),
    {NewMsg, DelWorldList} = check_key_world(unicode:characters_to_list(Msg1), [], []),
    case DelWorldList == [] of
        true ->
            {NewMsg1, DelWorldList1} = check_key_world(unicode:characters_to_list(Msg), [], []),
            {NewMsg1, DelWorldList1, undefined};
        _ ->
            {NewMsg, DelWorldList, undefined}
    end.

%% @doc 实际替换
check_key_world([], Msg, DelWorldList) ->
    {Msg, DelWorldList};
check_key_world([H | T], Msg, DelWorldList) ->
    case key_world_data:get(H) of
        Keyword = {_, _, _} ->
            case do_check_key_world(Keyword, T, [Keyword]) of
                {Word, LastT} ->
                    %% 需要替换的敏感词
                    DelMsg = [Last || {Last, _, _} <- Word],
                    check_key_world(LastT, Msg ++ "{face_1|22}", [DelMsg | DelWorldList]);
                _ ->
                    check_key_world(T, Msg ++ [H], DelWorldList)
            end;
        _ ->
            check_key_world(T, Msg ++ [H], DelWorldList)
    end.

do_check_key_world({_, IsEnd, _}, [], AddH) ->
    case IsEnd == ?true of
        true ->
            {AddH, []};
        _ ->
            false
    end;
do_check_key_world({_H, _IsEnd, Map}, [Last | T], AddH) ->
    case maps:get(Last, Map, ?undefined) of
        ?undefined ->
            case lists:last(AddH) of
                {_, ?true, _} ->
                    {AddH, [Last | T]};
                _ ->
                    false
            end;
        {Last, Is, LMap} ->
            do_check_key_world({Last, Is, LMap}, T, AddH ++ [{Last, Is, LMap}])
    end.


%% @doc 检查名字是否可用
check(name, String) ->
    case do_check_name(String) of
        false -> false;
        true -> do_check(String)
    end;
check(_, String) ->
    do_check(String).


do_check(String) ->
    case check_key_world(String) of
        {_, [], _} -> true;
        _ ->
            false
    end.

%% @doc 去除特殊字符
do_check_name(Bin) when is_binary(Bin) ->
    do_check_name(unicode:characters_to_list(Bin));
do_check_name(Str) ->
    Rule = "[\x{3000}\x{3010}折哲铳茺冲充銃珫直值群裙珺峮羣筘扣筘伍零二三四五六七八九〇①②③④⑤⑥⑦⑧⑨伍折哲群裙珺铳茺直值冲充0123456789]",
    case re:run(Str, Rule, [{capture, none}, caseless, unicode]) of%% 过滤掉【　这两个特殊符号
        match -> false;
        _ -> do_check_name_f1(Str)
    end.

do_check_name_f1([]) -> true;
do_check_name_f1([C | T])
    when (C >= $0 andalso C =< $9)
    orelse (C >= $a andalso C =< $z)
    orelse (C >= $A andalso C =< $Z)
    orelse C == $_
    -> do_check_name_f1(T);
do_check_name_f1([C | _]) when C =< 127 -> false;
do_check_name_f1([_C | T]) -> do_check_name_f1(T).